Skip to main content

Working with GPIOs on NVIDIA Jetson with libgpiod

Introduction

  • In this tutorial, we will learn how to read and toggle GPIOs on an NVIDIA Jetson module using libgpiod.
  • libgpiod is the modern, userspace approach to GPIO control on Linux systems, replacing the deprecated sysfs interface.
  • By the end of this guide, you'll be able to control GPIO pins, read their states, and toggle them from the command line or within Python/C applications.

What You Will Need

Before we dive into GPIO control, make sure you have the following:

  • NVIDIA Jetson module with JetPack 6.2.2
    A Jetson device (Orin NX, Orin Nano, AGX Orin, etc.) running JetPack 6.2.2 or later with Ubuntu-based L4T.

  • libgpiod installed
    GPIO control library and command-line tools for userspace GPIO access.

  • GPIO pins to test
    Access to GPIO pins on your Aerium carrier board (e.g., Lumen, Helios) or expansion headers.

  • Terminal access
    Local or SSH terminal access to your Jetson device.

  • Basic knowledge
    Familiarity with Linux command line and GPIO concepts.

Prerequisites

Step 1: Verify JetPack Version

Verify that your Jetson is running JetPack 6.2.2 or later:

cat /etc/nv_tegra_release

You should see output similar to:

R36 (release), REVISION: 5.0...[the rest of the line...]

Step 2: Install libgpiod

If not already installed, install the libgpiod library and tools:

sudo apt update
sudo apt install -y libgpiod-dev libgpiod-doc gpiod

Verify the installation:

gpioinfo --version

Step 3: Enabling GPIO Pins with jetson-io.py

Before you can control GPIO pins from userspace, you need to make sure they are exported and not reserved by the system. NVIDIA provides a configuration utility called jetson-io.py that lets you select which header or board pins are exposed as GPIOs.

  1. Run the utility with root privileges:

    sudo /opt/nvidia/jetson-io/jetson-io.py
  2. Select Configure Jetson 40Pin Header. jetson-io-gpio

  3. Select Configure Pin Heades Manually. jetson-io-gpio

  4. Select Move to the desired interface and click Enter to move the selection to GPIO. jetson-io-gpio

  5. After making your selections, choose Save and reboot to reconfigure pins and reboot the Jetson device when prompted. jetson-io-gpio

Understanding GPIO Numbering

NVIDIA Jetson devices use a hierarchical GPIO naming convention. To identify available GPIO pins and their current states.

gpioinfo

This command lists all GPIO chips and their pins. Output will show:

gpiochip0 - 225 lines:
line 0: "{NAME}" unused input active-high
line 1: "{NAME}" unused input active-high
...

To correlate a pin name to a line number use the pinmux table related to your NVIDIA Jetson.
For example, for Jetson Orin series, locating the SPI0_CS0 and SPI1_CS0 pins.
The pin names (PZ.06 and PY.03) have line numbers correlated in the gpioinfo output.
Those are the line numbers to be used with libgpiod.

jetson-io-gpio

Key information:

  • gpiochip0 - The GPIO controller chip
  • line X - The GPIO line number within that chip
  • State information (used/unused, input/output, active level)

Reading GPIO Values

Using gpioget Command

To read the current state of a GPIO pin:

gpioget gpiochip0 <line_number>

Example - Read GPIO line 232:

gpioget gpiochip0 232

Output will be 0 (low) or 1 (high).

Reading Multiple GPIO Pins

Read multiple pins at once:

gpioget gpiochip0 232 233 234

Output example:

0 1 1

Reading with a Description

Use gpiofind to locate a GPIO by name if available:

gpiofind "GPIO_PIN_NAME"

Then read it:

gpioget $(gpiofind "GPIO_PIN_NAME")

Toggling (Writing to) GPIO Values

Setting GPIO as Output

To use a GPIO pin as an output, you must configure it first. Use the gpioset command:

gpioset gpiochip0 <line_number>=<value>

Where <value> is 0 (low) or 1 (high).

Example: Toggle a GPIO Pin

Set GPIO line 232 to high (1):

gpioset gpiochip0 232=1

Set it to low (0):

gpioset gpiochip0 232=0

NOTE: The pin must not be in use by a kernel driver. If you receive an "Device or resource busy" error, the pin is already claimed by the system.

Toggle with Hold Duration

To set a GPIO high and keep it high while the command runs:

gpioset --hold-period=5s gpiochip0 232=1

This keeps the pin high for 5 seconds, then automatically returns it to its default state.

Toggle Multiple GPIO Pins

Control multiple pins in a single command:

gpioset gpiochip0 232=1 233=0 234=1

Monitoring GPIO with gpiomon

To continuously monitor GPIO state changes:

gpiomon gpiochip0 <line_number>

Example - Monitor GPIO line 232:

gpiomon gpiochip0 232

Output example:

event:  RISING EDGE offset: 232 timestamp: [    1234.567890]
event: FALLING EDGE offset: 232 timestamp: [ 1235.234567]

Monitor multiple pins:

gpiomon gpiochip0 232 233 234

Press Ctrl+C to stop monitoring.

Advanced Usage with gpioset Flags

Set as Input with Pull Configuration

Modern versions of libgpiod support bias (pull-up/pull-down) configuration:

gpioset --bias=pull-up gpiochip0 232=1

Active Low Configuration

If your GPIO logic is inverted (active-low):

gpioset --active-low gpiochip0 232=1

Python Example: GPIO Control with libgpiod

Installation

pip3 install gpiod

Read GPIO

import gpiod

# Open the GPIO chip
chip = gpiod.Chip('gpiochip0')

# Get a specific line
line = chip.get_line(232)

# Request the line for input
config = gpiod.LineRequest([line], gpiod.LINE_REQ_DIR_IN)

# Read the value
value = config.get_value(0)
print(f"GPIO 232 state: {value}")

config.release()

Write/Toggle GPIO

import gpiod

# Open the GPIO chip
chip = gpiod.Chip('gpiochip0')

# Get a specific line
line = chip.get_line(232)

# Request the line for output
config = gpiod.LineRequest([line], gpiod.LINE_REQ_DIR_OUT)

# Set the line high
config.set_value(0, 1)
print("GPIO 232 set to HIGH")

# Set the line low
config.set_value(0, 0)
print("GPIO 232 set to LOW")

config.release()

Monitor GPIO Changes

import gpiod

chip = gpiod.Chip('gpiochip0')
line = chip.get_line(232)

# Request the line for input with event monitoring
config = gpiod.LineRequest([line], gpiod.LINE_REQ_DIR_IN | gpiod.LINE_REQ_EV_BOTH_EDGES)

print("Monitoring GPIO 232 for changes (Ctrl+C to exit)...")
try:
while True:
# Wait for events with 1 second timeout
if config.wait_event(1000):
events = config.get_events()
for event in events:
print(f"Event: {'RISING' if event.type == gpiod.LineEvent.RISING_EDGE else 'FALLING'} EDGE")
except KeyboardInterrupt:
print("\nMonitoring stopped")
finally:
config.release()

C Example: GPIO Control with libgpiod

Read GPIO

#include <gpiod.h>
#include <stdio.h>

int main() {
// Open the GPIO chip
struct gpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip) {
perror("gpiod_chip_open");
return 1;
}

// Get GPIO line 232
struct gpiod_line *line = gpiod_chip_get_line(chip, 232);
if (!line) {
perror("gpiod_chip_get_line");
gpiod_chip_close(chip);
return 1;
}

// Request the line as input
if (gpiod_line_request_input(line, "gpio-test") < 0) {
perror("gpiod_line_request_input");
gpiod_chip_close(chip);
return 1;
}

// Read the value
int value = gpiod_line_get_value(line);
printf("GPIO 232 state: %d\n", value);

// Release and close
gpiod_chip_close(chip);
return 0;
}

Compile with:

gcc -o gpio_read gpio_read.c -lgpiod
./gpio_read

Write/Toggle GPIO

#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>

int main() {
struct gpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip) {
perror("gpiod_chip_open");
return 1;
}

struct gpiod_line *line = gpiod_chip_get_line(chip, 232);
if (!line) {
perror("gpiod_chip_get_line");
gpiod_chip_close(chip);
return 1;
}

// Request the line as output
if (gpiod_line_request_output(line, "gpio-test", 0) < 0) {
perror("gpiod_line_request_output");
gpiod_chip_close(chip);
return 1;
}

// Toggle the GPIO
printf("Setting GPIO 232 to HIGH...\n");
gpiod_line_set_value(line, 1);
sleep(2);

printf("Setting GPIO 232 to LOW...\n");
gpiod_line_set_value(line, 0);
sleep(2);

gpiod_chip_close(chip);
return 0;
}

C++ Example: GPIO Control with libgpiod

Read GPIO

#include <gpiod.h>
#include <iostream>
#include <stdexcept>

class GPIOReader {
private:
gpiod_chip *chip;
gpiod_line *line;

public:
GPIOReader(const char *chip_name, unsigned int line_num) {
chip = gpiod_chip_open(chip_name);
if (!chip) {
throw std::runtime_error("Failed to open GPIO chip");
}

line = gpiod_chip_get_line(chip, line_num);
if (!line) {
gpiod_chip_close(chip);
throw std::runtime_error("Failed to get GPIO line");
}

if (gpiod_line_request_input(line, "gpio-test") < 0) {
gpiod_chip_close(chip);
throw std::runtime_error("Failed to request GPIO line as input");
}
}

int read() {
return gpiod_line_get_value(line);
}

~GPIOReader() {
if (chip) {
gpiod_chip_close(chip);
}
}
};

int main() {
try {
GPIOReader reader("/dev/gpiochip0", 232);
int value = reader.read();
std::cout << "GPIO 232 state: " << value << std::endl;
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

Compile with:

g++ -o gpio_read gpio_read.cpp -lgpiod
./gpio_read

Write/Toggle GPIO

#include <gpiod.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <stdexcept>

class GPIOWriter {
private:
gpiod_chip *chip;
gpiod_line *line;

public:
GPIOWriter(const char *chip_name, unsigned int line_num) {
chip = gpiod_chip_open(chip_name);
if (!chip) {
throw std::runtime_error("Failed to open GPIO chip");
}

line = gpiod_chip_get_line(chip, line_num);
if (!line) {
gpiod_chip_close(chip);
throw std::runtime_error("Failed to get GPIO line");
}

if (gpiod_line_request_output(line, "gpio-test", 0) < 0) {
gpiod_chip_close(chip);
throw std::runtime_error("Failed to request GPIO line as output");
}
}

void set(int value) {
gpiod_line_set_value(line, value);
}

void toggle(int duration_ms = 2000) {
std::cout << "Setting GPIO to HIGH...\n";
set(1);
std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));

std::cout << "Setting GPIO to LOW...\n";
set(0);
std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
}

~GPIOWriter() {
if (chip) {
gpiod_chip_close(chip);
}
}
};

int main() {
try {
GPIOWriter writer("/dev/gpiochip0", 232);
writer.toggle();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

Compile with:

g++ -o gpio_write gpio_write.cpp -lgpiod
./gpio_write

Troubleshooting

Error: "Device or resource busy"

The GPIO pin is already in use by a kernel driver or another process.

Solution:

  • Check if the pin is controlled by a kernel module
  • Use lsof /dev/gpiochip* to find processes using GPIO
  • Unbind the pin from its driver if possible

Error: "No such device"

The GPIO chip does not exist or is not accessible.

Solution:

  • Verify the chip exists: ls -la /dev/gpiochip*
  • Check user permissions: userspace GPIO access typically requires root or membership in the gpio group
  • Add your user to the gpio group: sudo usermod -aG gpio $USER

GPIO State Not Changing

The pin may be reserved by a kernel driver or the configuration may be incorrect.

Solution:

  • Verify the pin with gpioinfo
  • Check if another process is controlling the pin
  • Ensure you're using the correct chip number and line number

Summary

You now understand how to:

  • Discover available GPIO pins with gpioinfo
  • Read GPIO states with gpioget
  • Toggle GPIO outputs with gpioset
  • Monitor GPIO changes with gpiomon
  • Control GPIO from Python and C applications

libgpiod is the recommended approach for GPIO control on modern Linux systems and JetPack 6.2.2. It provides a clean, stable interface for GPIO operations in userspace.